use crate::limits::{total_limit, LimitError};

type ScalarFnT = fn(f64, &Option<Vec<f64>>) -> f64;

pub trait EvalScalar {
    fn eval(&self, x: f64) -> f64;
    fn limit(&self, limit_point: f64) -> Result<f64, LimitError>;
    fn get_fn(&self) -> Option<ScalarFnT>;
    fn get_params(&self) -> Option<Vec<f64>>;
}

#[derive(Clone, Debug)]
pub struct ScalarFn {
    func: ScalarFnT,
    params: Option<Vec<f64>>,
}

impl ScalarFn {
    pub fn new(func: ScalarFnT, params: Option<Vec<f64>>) -> Self {
        ScalarFn { func, params }
    }
}

impl EvalScalar for ScalarFn {
    fn eval(&self, x: f64) -> f64 {
        (self.func)(x, &self.params)
    }

    fn limit(&self, limit_point: f64) -> Result<f64, LimitError> {
        total_limit(limit_point, self)
    }

    fn get_fn(&self) -> Option<ScalarFnT> {
        Some(self.func)
    }

    fn get_params(&self) -> Option<Vec<f64>> {
        self.params.clone()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::math::{disco_line, line};

    #[test]
    fn test_limit_line() {
        let params = vec![1.0f64, 1.0f64];
        let limit_point = -1.0f64;
        let line = ScalarFn::new(line, Some(params));
        let result = line.limit(limit_point);
        match result {
            Ok(_) => assert!(true),
            Err(_) => assert!(false),
        }
    }

    #[test]
    fn test_limit_disco_line() {
        let limit_point = 1.0f64;
        let disco_line = ScalarFn::new(disco_line, None);
        let result = disco_line.limit(limit_point);
        match result {
            Ok(x) => {
                println!("Disco line converged to: {x}");
                assert!(true);
            }
            Err(x) => {
                println!("Disco line failed with: {:?}", x);
                assert!(false);
            }
        }
    }
}
